﻿using System;
using System.Linq;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Describes one voice of a <see cref="PartElement"/> in one <see cref="MeasureElement"/>.
    /// </summary>
    /// <remarks>
    /// The <see cref="VoiceDescriptorElement"/> class is used first to allow spare voices collection
    /// (it holds the identifier of the voice) and second to allow each voice to possibly have
    /// a different meter.
    /// </remarks>
    public class VoiceDescriptorElement : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="VoiceDescriptorElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="PartElement"/>
        /// </para>
        /// <para>
        /// The part that directly owns this voice.
        /// </para>
        /// </param>
        /// <param name="number">
        /// <para>
        /// Type: <see cref="System.Byte"/>
        /// </para>
        /// <para>
        /// The identifier of the voice.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        internal VoiceDescriptorElement(CompositionPart packagePart, PartElement owner, byte number)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            numberAttribute.Value = number.ToString(PackageVocabulary.PackageCulture.NumberFormat);
        }

        /// <summary>
        /// Creates a new <see cref="VoiceDescriptorElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="PartElement"/>
        /// </para>
        /// <para>
        /// The part that directly owns this voice.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal VoiceDescriptorElement(CompositionPart packagePart, PartElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            numberAttribute = element.Attributes[PackageVocabulary.NumberAttribute];
            meterElement = element[PackageVocabulary.MeterElementName, PackageVocabulary.PackageNamespaceUri];

            if (meterElement.HasChildNodes)
                meter = new TimeSignatureElement(packagePart, this, (XmlElement)meterElement.FirstChild);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.VoiceDescriptorElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            numberAttribute = document.CreateAttribute(PackageVocabulary.NumberAttribute);
            meterElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.MeterElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            element.Attributes.Append(numberAttribute);
            element.AppendChild(meterElement);

            return element;
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Iterates through the symbols that belong to the the described voice and finds the shortest duration.
        /// </summary>
        /// <remarks>
        /// This method should be invoked when a symbol with the same <see cref="DurationalSymbolElement.Duration"/>
        /// as the <see cref="ShortestDuration"/> is removed from the described voice.
        /// </remarks>
        /// <seealso cref="ShortestDuration"/>
        internal void RequeryShortestDuration()
        {
            var mySymbols = from beat in FindOwner<PartElement>().Content.Beats
                            where beat[this] != null
                            select beat[this];

            shortestDuration = mySymbols.Min(s => s.Duration);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the number of this voice.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Byte"/>
        /// </para>
        /// <para>
        /// The number of the voice.
        /// The value corresponds to the number of the described voice.
        /// The 1st voice has number 1.
        /// </para>
        /// </value>
        public byte Number
        {
            get { return byte.Parse(numberAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
        }

        /// <summary>
        /// Gets the meter of this voice.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="TimeSignatureElement"/>
        /// </para>
        /// <para>
        /// The meter of this voice inside this measure. This value may be <see langword="null"/>
        /// in which case the voice has the same meter as its owner <see cref="MeasureElement"/>.
        /// </para>
        /// </value>
        /// <seealso cref="MeasureElement.Meter"/>
        public TimeSignatureElement Meter
        {
            get { return meter; }
        }

        /// <summary>
        /// Gets the shortest duration that appears in the described voice.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The shortest duration that appears in the described voice in this part in this measure.
        /// This value is 0 if the voice is empty.
        /// </para>
        /// </value>
        public ushort ShortestDuration
        {
            get { return shortestDuration; }
            internal set
            {
                shortestDuration = value;

                RaisePropertyChanged("ShortestDuration");
            }
        }

        /// <summary>
        /// Gets the number of durational symbols in the described voice.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The number of durational symbols in the described voice in this part in this measure.
        /// </para>
        /// </value>
        public int SymbolsCount
        {
            get { return symbolsCount; }
            internal set
            {
                symbolsCount = value;

                RaisePropertyChanged("SymbolsCount");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// The XML attribute that holds the value of the <see cref="Number"/> property.
        /// </summary>
        private XmlAttribute numberAttribute;
        /// <summary>
        /// Holds the value of the <see cref="Meter"/> property.
        /// </summary>
        private TimeSignatureElement meter;
        /// <summary>
        /// The XML element that holds the value of the <see cref="Meter"/> property.
        /// </summary>
        private XmlElement meterElement;
        /// <summary>
        /// Holds the value of the <see cref="ShortestDuration"/> property.
        /// </summary>
        private ushort shortestDuration;
        /// <summary>
        /// Holds the value of the <see cref="SymbolsCount"/> property.
        /// </summary>
        private int symbolsCount;

        #endregion
    }
}
